package org.urbansoft.icl.reader;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import org.apache.log4j.Logger;
import org.urbansoft.icl.record.BundleControlRecord;
import org.urbansoft.icl.record.BundleHeaderRecord;
import org.urbansoft.icl.record.CashLetterControlRecord;
import org.urbansoft.icl.record.CashLetterHeaderRecord;
import org.urbansoft.icl.record.CheckDetailAddendumARecord;
import org.urbansoft.icl.record.CheckDetailRecord;
import org.urbansoft.icl.record.FileControlRecord;
import org.urbansoft.icl.record.FileHeaderRecord;
import org.urbansoft.icl.record.ImageViewAnalysisRecord;
import org.urbansoft.icl.record.ImageViewDataRecord;
import org.urbansoft.icl.record.ImageViewDetailRecord;
import org.urbansoft.icl.record.Record;
import org.urbansoft.icl.util.FileEncoding;
import org.urbansoft.icl.util.ICLException;
import org.urbansoft.icl.util.RecordLength;
import org.urbansoft.iso.ISOUtil;
import org.urbansoft.icl.record.CheckDetailAddendumCRecord;
import org.urbansoft.icl.record.ReturnAddendumARecord;
import org.urbansoft.icl.record.ReturnAddendumD3501;
import org.urbansoft.icl.record.ReturnAddendumD3502;
import org.urbansoft.icl.record.ReturnAddendumD3503;
import org.urbansoft.icl.record.ReturnAddendumD3504;
import org.urbansoft.icl.record.ReturnAddendumD3505;
import org.urbansoft.icl.record.ReturnAddendumD3506;
import org.urbansoft.icl.record.ReturnAddendumD3598;
import org.urbansoft.icl.record.ReturnAddendumD3599;
import org.urbansoft.icl.record.ReturnRecord;

/**
 * The Class ICLFileRecordReader.
 */
public class ICLFileRecordReader {
	private InputStream inputStream;
	private BufferedInputStream is;
	private ArrayList<Record> records;
	private byte[] fieldLength;
	private FileEncoding fileEncoding;

	private static Logger log = Logger.getLogger(ICLFileRecordReader.class);

	/**
	 * Instantiates a new iCL file record reader.
	 */
	public ICLFileRecordReader() {
		this.records = new ArrayList<Record>();
		fieldLength = new byte[4];
	}

	/**
	 * Instantiates a new iCL file record reader.
	 * 
	 * @param is
	 *            the is
	 */
	public ICLFileRecordReader(InputStream is, FileEncoding fileEncoding) {
		this.inputStream = is;
		this.records = new ArrayList<Record>();
		this.fileEncoding = fileEncoding;
		fieldLength = new byte[4];
	}

	/**
	 * Read.
	 * 
	 * @return the array list< record>
	 */
	public ArrayList<Record> read() {
		try {
			is = new BufferedInputStream(inputStream);

			int length;
			while ((length = getNextFieldLength()) > 0) {
				Record r = getNextRecord(length);
				r.setSize(length);
				// log.debug(r.toString());
				// if((r instanceof BundleControlRecord) && records.size() >
				// 59994){
				// records.add(r);
				// records.add(new CashLetterControlRecord(0));
				// records.add(new FileControlRecord(0));
				// break;
				// }
				records.add(r);
			}

			// int recordCountFromFile =
			// Integer.parseInt(records.get(records.size() - 1)
			// .getFieldDataAtPosition(3));
			// log.info("Imported: " + records.size() +
			// " records from ICL file");
			// log.info("Record count from file: " + recordCountFromFile);
			// if (records.size() != recordCountFromFile) {
			// //log.error("Record counts do not match.  Please check error log");
			// return null;
			// }

			is.close();
			inputStream.close();
		} catch (IOException e) {
			// log.error("Error reading input source file", e);
		}
		return records;
	}

	/**
	 * Gets the next record.
	 * 
	 * @param readSize
	 *            the read size
	 * 
	 * @return the next record
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	private Record getNextRecord(int readSize) throws IOException {
		byte[] recordBytes = new byte[readSize];
		Record rec = null;

		/* read from the file */
		is.read(recordBytes);
		if (fileEncoding == FileEncoding.EBCDIC) {
			recordBytes = ISOUtil.ebcdicToAsciiBytes(recordBytes);
		}
		/*
		 * this is a bit expensive and should probably just be done with the
		 * bytes TODO: speed this up
		 */
		String s = new String(recordBytes, 0, 2);
		// log.debug("Record type : " + s);
		if (s.matches("^01.*")) {
			rec = new FileHeaderRecord("");
		} else if (s.matches("^10.*")) {
			rec = new CashLetterHeaderRecord("");
		} else if (s.matches("^20.*")) {
			rec = new BundleHeaderRecord("");
		} else if (s.matches("^25.*")) {
			rec = new CheckDetailRecord("");
		} else if (s.matches("^26.*")) {
			rec = new CheckDetailAddendumARecord("");
		} else if (s.matches("^28.*")) {
			rec = new CheckDetailAddendumCRecord("");
		} else if (s.matches("^31.*")) {
			rec = new ReturnRecord();
		} else if (s.matches("^32.*")) {
			rec = new ReturnAddendumARecord();
		} else if (s.matches("^35.*")) {
			String s2 = new String(recordBytes, 2, 4);
			// log.info("Leyendo AddD35:" + s2);
			if (s2.matches("^01.*")) {
				rec = new ReturnAddendumD3501();
			} else if (s2.matches("^02.*")) {
				rec = new ReturnAddendumD3502();
			} else if (s2.matches("^03.*")) {
				rec = new ReturnAddendumD3503();
			} else if (s2.matches("^04.*")) {
				rec = new ReturnAddendumD3504();
			} else if (s2.matches("^05.*")) {
				rec = new ReturnAddendumD3505();
			} else if (s2.matches("^06.*")) {
				rec = new ReturnAddendumD3506();
			} else if (s2.matches("^98.*")) {
				rec = new ReturnAddendumD3598();
			} else if (s2.matches("^99.*")) {
				rec = new ReturnAddendumD3599();
			}
		} else if (s.matches("^50.*")) {
			rec = new ImageViewDetailRecord("");
		} else if (s.matches("^52.*")) {
			rec = new ImageViewDataRecord("");
		} else if (s.matches("^54.*")) {
			rec = new ImageViewAnalysisRecord();
		} else if (s.matches("^70.*")) {
			rec = new BundleControlRecord("");
		} else if (s.matches("^90.*")) {
			rec = new CashLetterControlRecord("");
		} else if (s.matches("^99.*")) {
			rec = new FileControlRecord("");
		} else {
			// log.error("Tipo de registro desconocido: [" + s + "]");
			// log.error("Full record: [" + new String(recordBytes, 0, readSize)
			// + "]");
			return null;
		}

		try {
			rec.populateFields(recordBytes);
		} catch (ICLException e) {
			// log.warn("Error populating fields from byte array", e);
		}

		return rec;
	}

	/**
	 * Gets the next field length.
	 * 
	 * @return the next field length
	 * 
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	private int getNextFieldLength() throws IOException {
		int numRead = is.read(fieldLength);
		if (numRead == -1) {
			// log.debug("EOF detected");
			return 0;
		} else if (numRead != 4) {
			// log.error("Error with ICL file.  Four bytes were expected for length but we read: "
			// + numRead + "bytes.");
		}
		int bytesToRead = RecordLength.byteArrayToInt(fieldLength);
		// log.debug("Next field is : " + bytesToRead + " bytes long.");
		return bytesToRead;
	}

	/**
	 * Gets the input stream.
	 * 
	 * @return the input stream
	 */
	public InputStream getInputStream() {
		return inputStream;
	}

	/**
	 * Sets the input stream.
	 * 
	 * @param inputStream
	 *            the new input stream
	 */
	public void setInputStream(InputStream inputStream) {
		this.inputStream = inputStream;
	}

	/**
	 * Gets the records.
	 * 
	 * @return the records
	 */
	public ArrayList<Record> getRecords() {
		return records;
	}

	public BufferedInputStream getIs() {
		return is;
	}

	public void setIs(BufferedInputStream is) {
		this.is = is;
	}

	public byte[] getFieldLength() {
		return fieldLength;
	}

	public void setFieldLength(byte[] fieldLength) {
		this.fieldLength = fieldLength;
	}

	public FileEncoding getFileEncoding() {
		return fileEncoding;
	}

	public void setFileEncoding(FileEncoding fileEncoding) {
		this.fileEncoding = fileEncoding;
	}
}
